/*
 * Copyright (c) 2009 VMware, Inc.
 * Copyright (c) 2009 John Pritchard, WTKX Project Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package wtkx.wtk;

import wtkx.Application;
import wtkx.Display;
import wtkx.collections.Dictionary;
import wtkx.collections.HashMap;
import wtkx.in.DisplayHost;
import wtkx.util.ResourceCacheDictionary;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.util.Iterator;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;

/**
 * Base class for application contexts.
 *
 * @author gbrown
 */
public abstract class ApplicationContext 
    extends Object
{

    private static class IntervalTask extends TimerTask {
        private Runnable runnable = null;

        public IntervalTask(Runnable runnable) {
            this.runnable = runnable;
        }

        public void run() {
            queueCallback(runnable);
        }
    }

    private static class TimeoutTask extends TimerTask {
        private Runnable runnable = null;
        int timeoutID = -1;

        public TimeoutTask(Runnable runnable, int timeoutID) {
            this.runnable = runnable;
            this.timeoutID = timeoutID;
        }

        public void run() {
            queueCallback(runnable, true);
            clearTimeout(timeoutID);
        }
    }


    private static Timer timer = new Timer(true);
    private static HashMap<Integer, TimerTask> timerTaskMap = new HashMap<Integer, TimerTask>();
    private static int nextTimerTaskID = 0;

    private static final String DEFAULT_THEME_CLASS_NAME = "wtkx.wtk.skin.terra.TerraTheme";


    /**
     * Opens the resource at the given location.
     *
     * @param location
     */
    public static void open(URL location) {
        // TODO Remove dynamic invocation when Java 6 is supported on the Mac

        try {
            Class<?> desktopClass = Class.forName("java.awt.Desktop");
            Method getDesktopMethod = desktopClass.getMethod("getDesktop", new Class<?>[] {});
            Method browseMethod = desktopClass.getMethod("browse", new Class[] {URI.class});
            Object desktop = getDesktopMethod.invoke(null, (Object[]) null);
            browseMethod.invoke(desktop, location.toURI());
        } catch (Exception exception) {
            System.out.println("Unable to open URL in default browser.");
        }
    }

    /**
     * Issues a system alert sound.
     */
    public static void beep() {
        java.awt.Toolkit.getDefaultToolkit().beep();
    }

    /**
     * Schedules a task for repeated execution. The task will be executed on the
     * UI thread.
     *
     * @param runnable
     * The task to execute.
     *
     * @param period
     * The interval at which the task should be executed.
     *
     * @return An integer ID that can be used to cancel execution of the task.
     */
    public static int setInterval(Runnable runnable, long period) {
        int intervalID = nextTimerTaskID++;

        IntervalTask intervalTask = new IntervalTask(runnable);
        timerTaskMap.put(intervalID, intervalTask);

        try {
            timer.schedule(intervalTask, 0, period);
        } catch (IllegalStateException exception) {
            System.out.println(exception.getMessage());

            // TODO This is a workaround for an apparent bug in the Mac OSX
            // Java Plugin, which appears to prematurely kill the timer thread.
            // Remove this when the issue is fixed.
            timer = new Timer(true);
            timerTaskMap.clear();
            timerTaskMap.put(intervalID, intervalTask);
            timer.schedule(intervalTask, 0, period);
        }

        return intervalID;
    }

    /**
     * Cancels execution of a scheduled task.
     *
     * @param intervalID
     * The ID of the task to cancel.
     */
    public static void clearInterval(int intervalID) {
        clearTimerTask(intervalID);
    }

    /**
     * Schedules a task for execution after an elapsed time.
     *
     * @param runnable
     * The task to execute.
     *
     * @param timeout
     * The time after which the task should begin executing.
     */
    public static int setTimeout(Runnable runnable, long timeout) {
        int timeoutID = nextTimerTaskID++;

        TimeoutTask timeoutTask = new TimeoutTask(runnable, timeoutID);
        timerTaskMap.put(timeoutID, timeoutTask);

        try {
            timer.schedule(timeoutTask, timeout);
        } catch (IllegalStateException exception) {
            System.out.println(exception.getMessage());

            // TODO This is a workaround for an apparent bug in the Mac OSX
            // Java Plugin, which appears to prematurely kill the timer thread.
            // Remove this when the issue is fixed.
            timer = new Timer(true);
            timerTaskMap.clear();
            timerTaskMap.put(timeoutID, timeoutTask);
            timer.schedule(timeoutTask, timeout);
        }

        return timeoutID;
    }

    /**
     * Cancels execution of a scheduled task.
     *
     * @param timeoutID
     * The ID of the task to cancel.
     */
    public static void clearTimeout(int timeoutID) {
        clearTimerTask(timeoutID);
    }

    /**
     * Cancels execution of a timer task.
     *
     * @param timerTaskID
     */
    private static void clearTimerTask(int timerTaskID) {
        TimerTask timerTask = timerTaskMap.remove(timerTaskID);
        if (timerTask != null) {
            timerTask.cancel();
        }
    }

    /**
     * Queues a task to execute after all pending events have been processed and
     * returns without waiting for the task to complete.
     *
     * @param runnable
     * The runnable to execute.
     */
    public static void queueCallback(Runnable runnable) {
        queueCallback(runnable, false);
    }

    /**
     * Queues a task to execute after all pending events have been processed and
     * optionally waits for the task to complete.
     *
     * @param runnable
     * The runnable to execute.
     *
     * @param wait
     * If <tt>true</tt>, does not return until the runnable has executed.
     * Otherwise, returns immediately.
     */
    public static void queueCallback(Runnable runnable, boolean wait) {
        if (wait) {
            try {
                java.awt.EventQueue.invokeAndWait(runnable);
            }
            catch (InvocationTargetException exception) {
            }
            catch (InterruptedException exception) {
            }
        }
        else 
            java.awt.EventQueue.invokeLater(runnable);
    }


    private final Dictionary<String,String> properties;
    private final Application application;
    private final DisplayHost displayHost;
    private final Display display;

    private URL origin = null;

    private ResourceCacheDictionary resourceCache = new ResourceCacheDictionary();


    protected ApplicationContext(Dictionary<String,String> properties, Application application) {
        super();
        this.properties = properties;
        this.application = application;
        this.displayHost = new DisplayHost(this);
        this.display = new Display(this,this.displayHost);
        try {
            Class themeClass = Class.forName(DEFAULT_THEME_CLASS_NAME);
            Theme.setTheme((Theme)themeClass.newInstance());
        }
        catch (Exception exc) {
        }
    }
    protected ApplicationContext(Dictionary<String,String> properties, Application application, URL origin) {
        this(properties,application);
        this.origin = origin;
    }


    public final Application getApplication() {
        return application;
    }
    public final DisplayHost getDisplayHost() {
        return displayHost;
    }
    public final Display getDisplay() {
        return display;
    }
    public boolean hasOrigin(){
        return (null != this.origin);
    }
    public URL getOrigin() {
        URL origin = this.origin;
        if (null == origin)
            throw new IllegalStateException();
        else
            return origin;
    }
    public Dictionary<String, String> getProperties(){
        return properties;
    }
    public ResourceCacheDictionary getResourceCache() {
        return resourceCache;
    }
}
